﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using Flagwind.Data.Entity;

namespace Flagwind.Data
{
	/// <summary>
	/// 数据实体基础类。
	/// </summary>
	public abstract class EntityBase : IEntity
	{
		#region 私有字段

		private string[] _propertyNames;
		private object[] _propertyValues;
		private bool[] _changedList;
		private string _tableName;
		private string _changedFieldName = string.Empty;
		private bool _isTestWriteProperty;
		private string _foreignKeys = string.Empty;

		#endregion

		#region 公共事件

		/// <summary>
		/// 属性获取事件。
		/// </summary>
		public event EventHandler<PropertyGettingEventArgs> PropertyGetting;

		/// <summary>
		/// 属性改变事件
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region 保护属性

		/// <summary>
		/// 实体类的映射类型
		/// </summary>
		protected MappingType MappingType
		{
			get;
			set;
		}

		/// <summary>
		/// 实体类对应的映射名称。
		/// </summary>
		protected string MappingName
		{
			get
			{
				if(this.MappingType == MappingType.SqlMap)
				{
					var index = this._tableName.LastIndexOf('.');
					if(index > 0)
					{
						return this._tableName.Substring(index + 1);
					}
					else
					{
						return this._tableName;
					}
				}

				return this._tableName;
			}
			set
			{
				this._tableName = value;
			}
		}

		/// <summary>
		/// 实体类映射的表的架构名字。
		/// </summary>
		protected string Scheme
		{
			get;
			set;
		}

		/// <summary>
		/// 标识字段名称（有些数据库可能内置不支持），该字段不可更新，但是插入数据的时候可以获取该字段
		/// </summary>
		protected string Identity
		{
			get;
			set;
		}

		/// <summary>
		/// 获取主键字段名称列表。
		/// </summary>
		protected List<string> PrimaryKey
		{
			get;
			set;
		}

		/// <summary>
		/// 获取属性名数组。
		/// </summary>
		protected virtual string[] PropertyNames
		{
			get
			{
				if(this._propertyNames == null)
				{
					this.SetFieldNames();
					this._changedList = new bool[this._propertyNames.Length];
				}
				return this._propertyNames;
			}
			set
			{
				this._propertyNames = value;
				this._changedList = new bool[this._propertyNames.Length];
			}
		}

		/// <summary>
		/// 获取属性值数组。
		/// </summary>
		protected virtual object[] PropertyValues
		{
			get
			{
				return this._propertyValues ?? (this._propertyValues = new object[PropertyNames.Length]);
			}
			set
			{
				// 重新设置实体类的值的时候，只修改状态
				this._changedList = new bool[this._propertyNames.Length];
				this._propertyValues = value;
			}
		}

		/// <summary>
		/// 属性值被改变的属性名列表。
		/// </summary>
		protected List<string> PropertyChangedList
		{
			get
			{
				var list = new List<string>();
				if(this.PropertyNames.Length > 0)
				{
					for(int i = 0; i < this._changedList.Length; i++)
					{
						if(this._changedList[i])
						{
							list.Add(PropertyNames[i]);
						}
					}
				}
				return list;
			}
		}

		#endregion

		#region 索引器

		/// <summary>
		/// 获取或者设置指定属性名称的值，属性名必须是一个实体类的属性（调用了GetProperty 和 SetProperty方法），不能是普通属性。
		/// 如果属性不存在，获取该属性值将为 null，而设置该属性值将抛出异常。
		/// </summary>
		/// <param name="propertyName">属性名称。</param>
		/// <returns>属性值。</returns>
		public object this[string propertyName]
		{
			get
			{
				EntityInfo ef = EntityFieldsCache.Item(this.GetType());
				string fieldName = ef.GetFieldName(propertyName);
				if(fieldName != null)
				{
					this.OnPropertyGeting(fieldName);
					return GetPropertyValue(fieldName);
				}
				//获取虚拟的字段值
				return GetPropertyValue(propertyName);
				;
			}
			set
			{
				EntityInfo ef = EntityFieldsCache.Item(this.GetType());
				string fieldName = ef.GetFieldName(propertyName);
				if(fieldName != null)
				{
					//如果是实体类基础定义的字段，必须检查设置的值得类型
					if(value != null)
					{
						Type fieldType = ef.GetPropertyType(fieldName);
						if(value.GetType() != fieldType)
						{
							throw new ArgumentException("实体类的属性字段" + propertyName + " 需要" + fieldType.Name + " 类型的值，但准备赋予的值不是该类型！");
						}
					}
					this.SetFieldValue(fieldName, value);
				}
				else
				{
					//设置虚拟的字段值
					this.SetFieldValue(propertyName, value);
				}
			}
		}

		/// <summary>
		/// 获取指定索引位置的属性的值。
		/// </summary>
		/// <param name="index">索引位置。</param>
		/// <returns>属性值。</returns>
		public object this[int index]
		{
			get
			{
				if(index < 0 || index > this.PropertyNames.Length)
				{
					return null;
				}
				string fieldName = this.PropertyNames[index];
				this.OnPropertyGeting(fieldName);
				return GetPropertyValue(fieldName);
			}
		}

		#endregion

		#region 构造方法

		/// <summary>
		/// 初始化 EntityBase 类的新实例。
		/// </summary>
		public EntityBase()
		{
			this.PrimaryKey = new List<string>();
			this.InitMetaData();
		}

		#endregion

		#region 字段存取

		/// <summary>
		/// 根据指定的字段名获取属性值。
		/// </summary>
		/// <param name="fieldName">字段名称。</param>
		/// <param name="fieldType">字段类型。</param>
		/// <returns>属性值。</returns>
		public object GetFieldValue(string fieldName, TypeCode fieldType)
		{
			this.OnPropertyGeting(fieldName);
			object value = this.GetPropertyValue(fieldName);
			if(value == DBNull.Value || value == null)
			{
				switch(fieldType)
				{
					case TypeCode.String:
						return null;
					case TypeCode.DateTime:
						return new DateTime(1900, 1, 1);
					case TypeCode.Boolean:
						return false;
					case TypeCode.Int16:
					case TypeCode.Int32:
					case TypeCode.Int64:
					case TypeCode.UInt16:
					case TypeCode.UInt32:
					case TypeCode.UInt64:
						return 0;
					case TypeCode.Char:
						return null;
					default:
						return 0.0;
				}
			}
			return value;
		}

		/// <summary>
		/// 根据指定的字段名获取属性值。
		/// </summary>
		/// <typeparam name="T">字段类型。</typeparam>
		/// <param name="fieldName">字段名称。</param>
		/// <returns>属性值。</returns>
		public T GetFieldValue<T>(string fieldName)
		{
			this.OnPropertyGeting(fieldName);
			return Utility.ConvertType<T>(GetPropertyValue(fieldName));
		}

		/// <summary>
		/// 根据指定的字段名设置属性值。
		/// </summary>
		/// <param name="fieldName">字段名称。</param>
		/// <param name="value">属性值。</param>
		public void SetFieldValue(string fieldName, object value)
		{
			_changedFieldName = fieldName;
			if(_isTestWriteProperty)
			{
				return;
			}
			int index = GetPropertyIndex(fieldName);
			if(index >= 0)
			{
				PropertyValues[index] = value;
				this.OnPropertyChanged(new PropertyChangedEventArgs(fieldName));
				_changedList[index] = true;
				return;
			}
			//可能实体类来自Select 部分字段
			//备份原来的名值组
			string[] namesTemp = PropertyNames;
			object[] valuesTemp = PropertyValues;
			bool[] changesTemp = _changedList;
			//重置字段名数组，names 为空,PropertyNames调用将会触发调用子类重载的 SetFieldNames 方法。
			_propertyNames = null;
			_propertyValues = null;
			//复制值
			bool setValueFlag = false;
			for(int i = 0; i < PropertyNames.Length; i++)
			{
				string name = PropertyNames[i];
				if(fieldName == name)
				{
					setValueFlag = true;
					PropertyValues[i] = value;
					_changedList[i] = true;
				}
				else
				{
					//如果未找到，说明原来实例对象的属性字段不在实体类的定义的属性字段中,否则，复制值
					for(int k = 0; k < namesTemp.Length; k++)
					{
						if(namesTemp[k] == name)
						{
							PropertyValues[i] = valuesTemp[k];
							_changedList[i] = changesTemp[k];
							break;
						}
					}
				}
			}
			if(!setValueFlag)
			{
				//要赋值的字段不在实体类定义的字段名中，抛出异常
				throw new ArgumentException("属性字段名称 [" + fieldName + "] 无效，请检查实体类的当前属性定义和重载的 SetFieldNames 方法中对 PropertyNames 的设置。");
			}
		}

		/// <summary>
		/// 根据指定的字段名设置属性值。
		/// </summary>
		/// <param name="fieldName">字段名称。</param>
		/// <param name="value">属性值。</param>
		/// <param name="size">字段大小。</param>
		public void SetFieldValue(string fieldName, string value, int size)
		{
			if(value != null && size > 0 && value.Length > size)
			{
				throw new Exception("字段" + fieldName + "的实际长度超出了最大长度" + size);
			}
			this.SetFieldValue(fieldName, value);
		}

		/// <summary>
		/// 获取设置过的字段名称。
		/// </summary>
		/// <returns>字段名称。</returns>
		public string GetChangedFieldName()
		{
			return this._changedFieldName;
		}

		/// <summary>
		/// 获取指定字段的大小。
		/// </summary>
		/// <param name="fieldName">字段名称。</param>
		/// <returns>字段大小。</returns>
		public int GetFieldSize(string fieldName)
		{
			return FieldSizeUtility.GetFieldSize(this.GetType().FullName, fieldName);
		}

		/// <summary>
		/// 设置指定字段的大小。
		/// </summary>
		/// <param name="fieldName">字段名称。</param>
		/// <param name="fieldSize">字段大小。</param>
		public void SetFieldSize(string fieldName, int fieldSize)
		{
			FieldSizeUtility.SetFieldSize(this.GetType().FullName, fieldName, fieldSize);
		}

		#endregion

		#region 属性存取

		/// <summary>
		/// 获取实体类的属性名/值对。
		/// </summary>
		/// <returns>属性名/值对。</returns>
		public EntityEntry GetNameValues()
		{
			EntityEntry result = new EntityEntry();
			result.PropertyNames = this.PropertyNames;
			result.PropertyValues = this.GetPropertyValues();
			return result;
		}

		/// <summary>
		/// 获取实体类属性改变过的属性名/值对。
		/// </summary>
		/// <returns>属性名/值对。</returns>
		public EntityEntry GetChangedNameValues()
		{
			EntityEntry result = new EntityEntry();
			List<string> names = new List<string>();
			List<object> values = new List<object>();
			for(int i = 0; i < this.PropertyNames.Length; i++)
			{
				if(this._changedList[i])
				{
					names.Add(this.PropertyNames[i]);
					values.Add(this.GetPropertyValues()[i]);
				}
			}
			result.PropertyNames = names.ToArray();
			result.PropertyValues = values.ToArray();
			return result;
		}

		/// <summary>
		/// 设置所有属性的值。
		/// </summary>
		/// <param name="values">数据值数组。</param>
		public void SetPropertyValues(object[] values)
		{
			if(values.Length != this.PropertyNames.Length)
			{
				throw new ArgumentException("要设置的值数组大小跟属性名数量不一致。");
			}
			this.PropertyValues = values;
		}

		/// <summary>
		/// 新增实体虚拟字段属性，用来传递内容。
		/// </summary>
		/// <param name="name">虚拟字段名称。</param>
		public void AddPropertyName(string name)
		{
			if(string.IsNullOrEmpty(name))
			{
				return;
			}
			var strA = string.Empty;
			int count = this.PropertyNames.Length + 1;
			string[] buffer = new string[count];
			this.PropertyNames.CopyTo(buffer, 0);
			buffer[count - 1] = name;
			this.PropertyNames = buffer;
			object[] tempValue = new object[count];
			this.GetPropertyValues().CopyTo(tempValue, 0);
			this.PropertyValues = tempValue;
			bool[] tempChanges = new bool[count];
			this._changedList.CopyTo(tempChanges, 0);
			this._changedList = tempChanges;
		}

		/// <summary>
		/// 获取属性的位置索引，如果找不到，返回-1
		/// </summary>
		/// <param name="propertyName">属性名。</param>
		/// <returns>属性的位置索引，如果找不到，返回-1。</returns>
		public int GetPropertyIndex(string propertyName)
		{
			if(string.IsNullOrEmpty(propertyName))
			{
				return -1;
			}
			int length = propertyName.Length;
			for(int i = 0; i < this.PropertyNames.Length; i++)
			{
				var buffer = this.PropertyNames[i];
				if(buffer != null && buffer.Length == length && string.Equals(buffer, propertyName, StringComparison.OrdinalIgnoreCase))
				{
					return i;
				}
			}
			return -1;
		}

		/// <summary>
		/// 获取属性列的值。
		/// </summary>
		/// <param name="propertyName">属性名称。</param>
		/// <returns>属性值。</returns>
		public object GetPropertyValue(string propertyName)
		{
			int index = this.GetPropertyIndex(propertyName);
			return index == -1 ? null : PropertyValues[index];
		}

		/// <summary>
		/// 获取属性的值，如果没有，将返回属性类型的默认值。
		/// 注意该方法调用将发起属性获取事件。
		/// </summary>
		/// <param name="propertyName">属性名称。</param>
		/// <returns>属性值。</returns>
		public object GetPropertyValueWithEvent(string propertyName)
		{
			var info = EntityFieldsCache.Item(this.GetType());
			string fieldName = info.GetFieldName(propertyName);
			if(fieldName != null)
			{
				this.OnPropertyGeting(fieldName);
				var result = GetPropertyValue(fieldName);
				if(result == null || result == DBNull.Value)
				{
					var type = info.GetPropertyType(fieldName);
					//活动类型的默认值
					result = Activator.CreateInstance(type);
				}
				return result;
			}
			return new ArgumentOutOfRangeException("不存在指定的属性名：" + propertyName);
		}

		#endregion

		#region 属性状态改变

		/// <summary>
		/// 重置实体类全部属性的修改标记。
		/// <remarks>注意，DataAccess 将根据该标记决定更新哪些字段到数据库，它只更新标记为已经修改的实体类属性。</remarks>
		/// </summary>
		/// <param name="flag">是否已经修改。</param>
		public void ResetChanges(bool flag = false)
		{
			if(this._changedList != null)
			{
				for(int i = 0; i < this._changedList.Length; i++)
				{
					this._changedList[i] = flag;
				}
			}
		}

		/// <summary>
		/// 设置属性的值是否跟默认值一样，如果一样则表示该属性未更改过。
		/// </summary>
		public void SetDefaultChanges()
		{
			for(int i = 0; i < this.GetPropertyValues().Length; i++)
			{
				var value = this.GetPropertyValues()[i];
				if(value != null && value != DBNull.Value)
				{
					var type = value.GetType();
					if(type.IsValueType)
					{
						var newValue = Activator.CreateInstance(type);
						//等于默认值，未改变
						this._changedList[i] = !newValue.Equals(value);
					}
					else if(type == typeof(string))
					{
						this._changedList[i] = value.ToString().Length > 0;
					}
				}
				else
				{
					//该属性未设置任何值，或者是字符串属性
					this._changedList[i] = false;
				}
			}
		}

		#endregion

		#region 实体映射

		/// <summary>
		/// 将实体类的表名称映射到一个新的表名称。
		/// </summary>
		/// <param name="name">新的表名称。</param>
		/// <returns>是否成功。</returns>
		public bool ChangeMappingTable(string name)
		{
			if(MappingType == MappingType.Table)
			{
				this.MappingName = name;
				return true;
			}
			return false;
		}

		/// <summary>
		/// 设置对应于父实体类的外键字段名称。
		/// </summary>
		/// <typeparam name="Parent">父实体类。</typeparam>
		/// <param name="fieldName">外键字段名称，必须是当前实体类使用的一个字段。</param>
		protected internal void SetForeignKey<Parent>(string fieldName) where Parent : IEntity, new()
		{
			var parent = new Parent();
			this._foreignKeys += "," + fieldName + "@" + parent.GetMappingName();
		}

		/// <summary>
		/// 获取对应的父实体类的外键字段名称，如果没有，返回空字符串。
		/// </summary>
		/// <typeparam name="Parent">父实体类。</typeparam>
		/// <returns>外键字段名称，必须是当前实体类使用的一个字段。</returns>
		public string GetForeignKey<Parent>() where Parent : IEntity, new()
		{
			Parent p = new Parent();
			foreach(string str in this._foreignKeys.Split(new char[]
			{
				','
			}, StringSplitOptions.RemoveEmptyEntries))
			{
				var buffer = str.Split('@');
				if(buffer[1] == this.GetMappingName())
				{
					return buffer[0];
				}
			}
			return string.Empty;
		}

		/// <summary>
		/// 从POCO实体类获取跟当前实体类的属性名称相同的属性的值，拷贝到当前实体类中，完成数据的映射。
		/// 要求拷贝的同名属性是读写属性且类型相同。
		/// </summary>
		/// <param name="pocoClass">POCO实体类，提供源数据。</param>
		/// <param name="isChange">是否改变属性的修改状态。</param>
		/// <returns>映射成功的属性数量。</returns>
		public int MapFrom(object pocoClass, bool isChange = false)
		{
			if(pocoClass == null)
			{
				return 0;
			}
			int count = 0;
			int fcount = this.PropertyNames.Length;
			var accessors = new INamedMemberAccessor[fcount];
			var drm = new MemberAccessor();
			var type = pocoClass.GetType();
			var info = EntityFieldsCache.Item(this.GetType());
			for(int i = 0; i < fcount; i++)
			{
				//实体类的属性字段可能跟属性名称不一样
				string perpertyName = info.GetPropertyName(PropertyNames[i]);
				accessors[i] = drm.TryFindAccessor(type, perpertyName);
			}
			for(int i = 0; i < fcount; i++)
			{
				if(accessors[i] != null)
				{
					this.GetPropertyValues()[i] = accessors[i].GetValue(pocoClass);
					//设置属性修改状态
					if(isChange)
					{
						this._changedList[i] = true;
					}
					count++;
				}
			}
			return count;
		}

		/// <summary>
		/// 将当前实体类的属性值（非空值）映射到相同属性名称的POCO实体类中。要求拷贝的同名属性是读写属性且类型相同。
		/// </summary>
		/// <param name="pocoClass">POCO实体类。</param>
		/// <returns>映射成功的属性数量。</returns>
		public int MapTo(object pocoClass)
		{
			if(pocoClass == null)
			{
				return 0;
			}
			int count = 0;
			int fcount = this.PropertyNames.Length;
			var accessors = new INamedMemberAccessor[fcount];
			var drm = new MemberAccessor();
			var type = pocoClass.GetType();
			var info = EntityFieldsCache.Item(this.GetType());
			for(int i = 0; i < fcount; i++)
			{
				//实体类的属性字段可能跟属性名称不一样
				string perpertyName = info.GetPropertyName(PropertyNames[i]);
				accessors[i] = drm.TryFindAccessor(type, perpertyName);
			}
			for(int i = 0; i < fcount; i++)
			{
				if(accessors[i] != null)
				{
					object Value = this.GetPropertyValues()[i];
					if(Value != null && Value != DBNull.Value)
					{
						accessors[i].SetValue(pocoClass, this.GetPropertyValues()[i]);
						count++;
					}
				}
			}
			return count;
		}

		#endregion

		#region 虚拟方法

		/// <summary>
		/// 初始化元数据，比如在此方法中手工设置子实体类与父实体类的外键关系，
		/// 如果在用户的分部类文件中重写该方法，可以防止代码生成器覆盖该方法。
		/// </summary>
		protected virtual void InitMetaData()
		{
		}

		/// <summary>
		/// 获取属性时调用。
		/// </summary>
		/// <param name="propertyName">属性名。</param>
		protected virtual void OnPropertyGeting(string propertyName)
		{
			if(this.PropertyGetting != null)
			{
				this.PropertyGetting(this, new PropertyGettingEventArgs(propertyName));
			}
		}

		/// <summary>
		/// 触发属性改变事件。
		/// </summary>
		/// <param name="e">属性改变事件对象。</param>
		protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
		{
			if(this.PropertyChanged != null)
			{
				this.PropertyChanged(this, e);
			}
		}

//		/// <summary>
//		/// 获取表名称，如果实体类有分表策略，那么请重写该方法。
//		/// </summary>
//		/// <returns></returns>
//		protected virtual string GetMappingName()
//		{
//			return this._tableName;
//		}

		/// <summary>
		/// 设置实体类对应的字段名称数组。
		/// 新版本子类可以实现这个细节，否则框架将反射获得该信息。
		/// </summary>
		protected virtual void SetFieldNames()
		{
			//如果子类未重写该方法，调用框架的数据库元数据获取方法。
			var info = EntityFieldsCache.Item(this.GetType());
			this._propertyNames = info.FieldNames;
		}

		/// <summary>
		/// 获取带架构的表名称，带中括号。
		/// </summary>
		/// <returns>带架构的表名称。</returns>
		public string GetSchemeTableName()
		{
			if(!string.IsNullOrEmpty(this.Scheme))
			{
				return string.Format("[{0}].[{1}]", this.Scheme, this.GetMappingName());
			}
			else
			{
				return string.Format("[{0}]", this.GetMappingName());
			}
		}

		#endregion

		#region 其他方法

		/// <summary>
		/// 测试写入属性。
		/// </summary>
		public void TestWriteProperty()
		{
			this._isTestWriteProperty = true;
		}

		/// <summary>
		/// 获取当前对象的浅表副本。
		/// </summary>
		/// <returns>当前对象的浅表副本。</returns>
		public object Clone()
		{
			return this.MemberwiseClone();
		}

		#endregion

		#region 映射方法

		/// <summary>
		/// 实体类的映射类型。
		/// </summary>
		public MappingType GetMappingType()
		{
			return this.MappingType;
		}

		/// <summary>
		/// 实体类对应的映射名称。
		/// </summary>
		public string GetMappingName()
		{
			return this.MappingName;
		}

		/// <summary>
		/// 实体类映射的表的架构名字。
		/// </summary>
		public string GetScheme()
		{
			return this.Scheme;
		}

		/// <summary>
		/// 标识字段名称（有些数据库可能内置不支持），该字段不可更新，但是插入数据的时候可以获取该字段。
		/// </summary>
		public string GetIdentity()
		{
			return this.Identity;
		}

		/// <summary>
		/// 获取主键字段名称列表。
		/// </summary>
		public List<string> GetPrimaryKey()
		{
			return this.PrimaryKey;
		}

		/// <summary>
		/// 获取属性名数组。
		/// </summary>
		public string[] GetPropertyNames()
		{
			return this.PropertyNames;
		}

		public void SetPropertyNames(string[] names)
		{
			this.PropertyNames = names;
		}

		/// <summary>
		/// 获取属性值数组。
		/// </summary>
		public object[] GetPropertyValues()
		{
			return this.PropertyValues;
		}

		/// <summary>
		/// 属性值被改变的属性名列表。
		/// </summary>
		public List<string> GetPropertyChangedList()
		{
			return this.PropertyChangedList;
		}

		#endregion
	}
}